package com.hjzd.ai.smartpractice.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.LongSerializationPolicy;
import com.google.gson.reflect.TypeToken;
import com.hjzd.ai.smartpractice.config.WebClientConfig;
import com.hjzd.ai.smartpractice.constant.LLMRole;
import com.hjzd.ai.smartpractice.constant.ServiceConstant;
import com.hjzd.ai.smartpractice.entity.DTO.ScoreRule;
import com.hjzd.ai.smartpractice.entity.DTO.TestResultExcel;
import com.hjzd.ai.smartpractice.entity.DTO.chat.*;
import com.hjzd.ai.smartpractice.listener.OpenAISubscriber;
import com.hjzd.ai.smartpractice.service.ApiService;
import com.hjzd.ai.smartpractice.service.RedisService;
import com.hjzd.ai.smartpractice.util.HttpClientUtils;
import com.hjzd.ai.smartpractice.util.SequenceAudioInputStream;
import com.hjzd.ai.smartpractice.util.res.ResponseModel;
import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import okhttp3.sse.EventSources;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.netty.http.client.HttpClient;

import javax.net.ssl.SSLException;
import javax.sound.sampled.*;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author pengyonglei
 * @version 1.0.0
 */
@Service
public class ApiServiceImpl implements ApiService {

	private static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").setLongSerializationPolicy(LongSerializationPolicy.STRING).disableHtmlEscaping().create();
	private static final Logger logger = LoggerFactory.getLogger(ApiServiceImpl.class);

	@Value("${ai-model.llm.url}")
	private String aiModelLlmUrl;

	@Value("${ai-model.llm.token}")
	private String aiModelLlmToken;

	@Value("${ai-model.llm.savaVoicePath}")
	private String saveVoicePath;

	@Value("${ai-model.llm.chatModelName}")
	private String chatModelName;

	@Value("${ai-model.asr.url}")
	private String asrUrl;
	@Value("${ai-model.tts.url}")
	private String ttsUrl;

	@Resource
	private RedisService redisService;

	@Resource
	private WebClientConfig webClientConfig;

	private WebClient webClient = null;

	@PostConstruct
	public void initWebClient() {
		if (this.webClient == null) {
			SslContext sslContext = null;
			try {
				sslContext = SslContextBuilder
						.forClient()
						.trustManager(InsecureTrustManagerFactory.INSTANCE)
						.build();
			} catch (SSLException e) {
				throw new RuntimeException(e);
			}
			SslContext finalSslContext = sslContext;
			HttpClient httpClient = HttpClient.create()
					.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 600000)
					.option(ChannelOption.SO_TIMEOUT, 600000)
					.secure(sslContextSpec -> sslContextSpec.sslContext(finalSslContext));

			ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
			this.webClient = webClientConfig.webClientBuilder()
					.clientConnector(connector)
					.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json")
					.build();
		}
	}


	@Override
	public Flux<String> chatCompletionsStream(String uniqueKey, String chatLLMType, String chatType, String prompt) {

		if (StringUtils.isEmpty(chatType)) {
			chatType = "chat";
		}
		// 默认大模型为qwen
		if (StringUtils.isBlank(chatLLMType)) {
			chatLLMType = "qwen2";
		}

		// 1. 根据唯一标识查询客户信息(去redis查询)
		String mockChatInfoStr = redisService.get(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey));
		List<ChatCompletionMessage> messageList = GSON.fromJson(mockChatInfoStr, new TypeToken<List<ChatCompletionMessage>>(){}.getType());

		ChatCompletionRequest request = new ChatCompletionRequest();
		request.setModel(chatModelName);
		request.setStream(true);
		if (chatLLMType.equals("zhongxin")
				|| chatLLMType.equals("douyin")
				|| chatLLMType.equals("simulate")
				|| chatLLMType.equals("zx_yuanmengjin")
				|| chatLLMType.equals("zx_fenqi")
		) {
			request.setSession_id(uniqueKey);
			request.setCharacter(redisService.get("character_" + uniqueKey));
		}
		if (chatType.equals("chat")) {
			if (chatLLMType.equals("zhongxin")
					|| chatLLMType.equals("douyin")
					|| chatLLMType.equals("simulate")
					|| chatLLMType.equals("zx_yuanmengjin")
					|| chatLLMType.equals("zx_fenqi")
			) {
				messageList = new ArrayList<>();
				ChatCompletionMessage message = new ChatCompletionMessage();
				message.setRole(LLMRole.USER.getRole());
				message.setContent(prompt);
				messageList.add(message);
			}
			request.setMessages(messageList);
		} else if (chatType.equals("suggestion")) {

			StringBuilder dialogue = new StringBuilder();
			for (ChatCompletionMessage message : messageList) {
				if (message.getRole().equals(LLMRole.USER.getRole())) {
					dialogue.append("\n").append("客服：").append(message.getContent());
				} else if (message.getRole().equals(LLMRole.ASSISTANT.getRole())){
					dialogue.append("\n").append("客户：").append(message.getContent());
				}
			}
			// TODO 设置AI建议的
			List<ChatCompletionMessage> reqMessageList = new ArrayList<>();
			ChatCompletionMessage message = new ChatCompletionMessage();
			message.setRole(LLMRole.USER.getRole());
			message.setContent("下面三个反引号里面的内容是一段电话催收场景中客户与客服的对话，\n```" + dialogue + "```\n请从语速检测、情绪识别、话术相关性、对话流畅度、投诉风险的维度给出建议，每个维度的建议不超过30个字。返回的示例参考三个#括起来的内容。\n" +
					"###\n" +
					"1、语速检测：建议客服保持稳定语速，确保客户理解信息，避免增加客户压力\n" +
					"2、情绪识别：建议客服培训情绪识别技巧，对客户情绪波动敏感，适时安抚。\n" +
					"3、话术相关性：建议调整话术，更多关注客户实际困难，提供实际帮助或延期方案，减少催收压力。\n" +
					"4、对话流畅度：建议客服使用开放式问题，鼓励客户表达，同时保持对话连贯，避免打断客户。\n" +
					"5、投诉风险：建议定期回顾催收策略，确保遵守法律法规，避免过度催收，减少客户投诉风险。\n" +
					"###");
			reqMessageList.add(message);
			request.setMessages(reqMessageList);
		}

		String finalChatType = chatType;
		String baseUrl = "qwen2".equals(chatLLMType) ? aiModelLlmUrl + "/v1/chat/completions"
				: "zhongxin".equals(chatLLMType) ? "http://127.0.0.1:5000/v1/chat/completions"
				: "douyin".equals(chatLLMType) ? "http://127.0.0.1:5001/v1/chat/completions"
				: "simulate".equals(chatLLMType) ? "http://127.0.0.1:5002/v1/chat/completions"
				: "zx_yuanmengjin".equals(chatLLMType) ? "http://127.0.0.1:5003/v1/chat/completions"
				: "zx_fenqi".equals(chatLLMType) ? "http://127.0.0.1:5004/v1/chat/completions"
				: "";
		logger.info(">>>> baseUrl={}, 请求参数={}", baseUrl, GSON.toJson(request));
		return Flux.create(emitter -> {
			OpenAISubscriber subscriber = new OpenAISubscriber(emitter, uniqueKey, redisService, finalChatType);
			Flux<String> openAiResponse = getChatResponse(request, baseUrl);
			openAiResponse.subscribe(subscriber);
			emitter.onDispose(subscriber);
		});
	}

	@Override
	public void chatCompletionsStream2(String uniqueKey, String prompt, String chatType, HttpServletResponse response) {
		long start = System.currentTimeMillis();
		response.setCharacterEncoding("UTF-8");
		response.setContentType("text/event-stream");

		OkHttpClient client = new OkHttpClient.Builder()
				.connectTimeout(10, TimeUnit.SECONDS)
				.writeTimeout(50, TimeUnit.SECONDS)
				.readTimeout(10, TimeUnit.MINUTES)
				.build();

		CountDownLatch latch = new CountDownLatch(1);

		EventSource.Factory factory = EventSources.createFactory(client);
		// 请求体
		ChatCompletionRequest chatCompletionRequest = new ChatCompletionRequest();
		chatCompletionRequest.setModel("Qwen2-72B-Instruct");
		chatCompletionRequest.setStream(true);
		// 组装历史记录
		String mockChatInfoStr = redisService.get(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey));
		List<ChatCompletionMessage> messageList = GSON.fromJson(mockChatInfoStr, new TypeToken<List<ChatCompletionMessage>>(){}.getType());
		chatCompletionRequest.setMessages(messageList);
		String json = GSON.toJson(chatCompletionRequest);

		okhttp3.RequestBody body = okhttp3.RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),json);
		// 请求对象
		Request request = new Request.Builder()
				.url(aiModelLlmUrl + "/v1/chat/completions")
				.post(body)
				.build();

		// 自定义监听器
		EventSourceListener eventSourceListener = new EventSourceListener() {

			private String contentAll = "";
			@Override
			public void onOpen(EventSource eventSource, Response response) {
				logger.info("onOpen");
			}

			@Override
			public void onEvent(EventSource eventSource, String id, String type, String data) {
				//   接受消息 data
				logger.info("onEvent Data: {}", data);
				try {
					if ("\n".equals(data)) {
						contentAll += "\n";
						response.getWriter().write("data:\n\n");
						response.getWriter().flush();
					} else {
						String[] dataArr = data.split("\\n");
						for (int i = 0; i < dataArr.length; i++) {
							String resultJson = dataArr[i];
							if (resultJson.equals("[DONE]")) {
								logger.info("请求结束。。。");
								ChatCompletionResponse chatCompletionResponse = new ChatCompletionResponse();
								chatCompletionResponse.setFinish_reason("stop");
								// 将对话完整的存到redis中
								String mockChatInfoStr = redisService.get(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey));
								List<ChatCompletionMessage> messageList = GSON.fromJson(mockChatInfoStr, new TypeToken<List<ChatCompletionMessage>>() {
								}.getType());
								ChatCompletionMessage message = new ChatCompletionMessage();
								message.setRole(LLMRole.ASSISTANT.getRole());
								message.setContent(contentAll);
								messageList.add(message);
								redisService.set(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey), GSON.toJson(messageList), 7, TimeUnit.DAYS);
//								String ttsAudioUrl = tts(uniqueKey, contentAll);
								chatCompletionResponse.setReplyAudioUrl("http://127.0.0.1/replayUrl");
								response.getWriter().write("data:" + GSON.toJson(chatCompletionResponse) + "\n");
								response.getWriter().flush();
							} else {
								ChatCompletionResponse chatCompletionResponse = GSON.fromJson(resultJson, ChatCompletionResponse.class);
								String singleContent = chatCompletionResponse.getChoices().get(0).getDelta().getContent();
								if (StringUtils.isNotBlank(singleContent)) {
									contentAll += singleContent;
								}
								logger.info("单次返回内容={}, 总的内容={}", singleContent, contentAll);
							}
							String chunkData = "data:" + resultJson;
							if (i == dataArr.length - 1) {
								chunkData += "\n\n";
							} else {
								chunkData += "\n";
							}
							response.getWriter().write(chunkData);
							response.getWriter().flush();
						}
					}
				} catch (Exception ex) {
					logger.error("onEvent Error. ex=", ex);
				}
			}

			@Override
			public void onClosed(EventSource eventSource) {
				logger.info("onClosed");
				// 保存对话记录
				latch.countDown();
			}
			@Override
			public void onFailure(EventSource eventSource, Throwable t, Response response) {
				logger.info("onFailure, 错误信息=", t);
				latch.countDown();
			}
		};

		// 创建事件
		factory.newEventSource(request, eventSourceListener);
		try {
			latch.await();
		} catch (InterruptedException e) {
			logger.error("countDownLatch Error, e=", e);
		}
		logger.info("返回结果，耗时:{}ms", System.currentTimeMillis() - start);
		response.setStatus(200);
	}

	private Flux<String> getChatResponse(ChatCompletionRequest request, String baseUrl) {
		return webClient.post()
				.uri(baseUrl)  // 替换为实际的 OpenAI 端点
				.bodyValue(GSON.toJson(request))     // 假设你正在发送一个查询请求
				.retrieve()
				.bodyToFlux(String.class);
	}

	@Override
	public ResponseModel<Map<String, Object>> chatCompletions(String uniqueKey, String prompt) {
		Map<String, Object> resultMap = new HashMap<>();
		boolean isFinished = false;
		if (prompt.contains("再见")) {
			isFinished = true;
		}
		// 1. 根据唯一标识查询客户信息(去redis查询)
		String mockChatInfoStr = redisService.get(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey));
		List<ChatCompletionMessage> messageList = GSON.fromJson(mockChatInfoStr, new TypeToken<List<ChatCompletionMessage>>(){}.getType());

		long s = System.currentTimeMillis();
		String resultJson = requestChatModel(messageList,false);
		logger.info("模拟客户回复耗时: {}ms", (System.currentTimeMillis() - s));

		ChatCompletionResponse response = GSON.fromJson(resultJson, ChatCompletionResponse.class);
		if (response != null) {
			ChatCompletionChoice choice = response.getChoices().getFirst();
			ChatCompletionMessage assistantMessage = choice.getMessage();
			messageList.add(assistantMessage);
			// 将对话完整的存到redis中
			redisService.set(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey), GSON.toJson(messageList), 7, TimeUnit.DAYS);
			String content = assistantMessage.getContent();
			resultMap.put("replyContent", content);
			// 3. 调用TTS(语音合成)接口，然后把语音合成的音频文件保存到本地，返回一个可以播放的url
//			long ttsS = System.currentTimeMillis();
//			String audioUrl = tts(uniqueKey, content);
//			logger.info("TTS合成耗时: {}ms，真实的地址：{}", (System.currentTimeMillis() - ttsS), audioUrl);
//			String replyAudioUrl = "https://demo.wangchaotech.com/smart_practice_audio/" + audioUrl;
			String replyAudioUrl = "https://demo.wangchaotech.com/smart_practice_audio/";
			resultMap.put("replyAudioUrl", replyAudioUrl);
			if (content.contains("再见")) {
				isFinished = true;
			}
		}
		resultMap.put("isFinished", isFinished);
		return ResponseModel.success(resultMap);
	}

	@Override
	public ResponseModel<Map<String, Object>> init() {
		Map<String, Object> resultMap = new HashMap<>();
		// 1. 生成对话唯一标识
		String uniqueKey = UUID.randomUUID().toString();
		resultMap.put("uniqueKey", uniqueKey);
		// 2. 生成客户信息
		CustomerInfo mockCustomer = null;
		String mockCustomerInfoStr = redisService.get(ServiceConstant.MOCK_CUSTOMER_INFO_KEY);
		if (StringUtils.isNotBlank(mockCustomerInfoStr)) {
			ArrayList<CustomerInfo> customerInfoList = GSON.fromJson(mockCustomerInfoStr, new TypeToken<ArrayList<CustomerInfo>>(){}.getType());
			mockCustomer = customerInfoList.get(new Random().nextInt(customerInfoList.size()));
			redisService.set(String.format(ServiceConstant.MOCK_CUSTOMER_UNIQUE_KEY, uniqueKey), GSON.toJson(mockCustomer), 7, TimeUnit.DAYS);
		}else {
			mockCustomerInfoStr = "[\n" +
					"    {\n" +
					"        \"name\": \"李娜\",\n" +
					"        \"gender\": \"女\",\n" +
					"        \"address\": \"上海市浦东新区世纪大道100号\",\n" +
					"        \"overDueDays\": 45,\n" +
					"        \"owedAmount\": \"8000元\",\n" +
					"        \"job\": \"上海文化传媒有限公司\"\n" +
					"    },\n" +
					"    {\n" +
					"        \"name\": \"王丽丽\",\n" +
					"        \"gender\": \"女\",\n" +
					"        \"address\": \"深圳市福田区深南大道200号\",\n" +
					"        \"overDueDays\": 75,\n" +
					"        \"owedAmount\": \"15000元\",\n" +
					"        \"job\": \"深圳电子科技有限公司\"\n" +
					"    },\n" +
					"    {\n" +
					"        \"name\": \"赵婷\",\n" +
					"        \"gender\": \"女\",\n" +
					"        \"address\": \"成都市武侯区人民南路四段48号\",\n" +
					"        \"overDueDays\": 90,\n" +
					"        \"owedAmount\": \"20000元\",\n" +
					"        \"job\": \"成都餐饮管理有限公司\"\n" +
					"    }\n" +
					"]";
			ArrayList<CustomerInfo> customerInfoList = GSON.fromJson(mockCustomerInfoStr, new TypeToken<ArrayList<CustomerInfo>>(){}.getType());
			mockCustomer = customerInfoList.get(new Random().nextInt(customerInfoList.size()));
			redisService.set(String.format(ServiceConstant.MOCK_CUSTOMER_UNIQUE_KEY, uniqueKey), GSON.toJson(mockCustomer), 7, TimeUnit.DAYS);
		}
		resultMap.put("customerInfo", mockCustomer);
		// 3. 生成客户的角色信息
//		String systemPrompt = generateCustomerPortraits();
		String systemPrompt = getCustomerRole();
		List<ChatCompletionMessage> messageList = new ArrayList<>();
		ChatCompletionMessage systemMessage = new ChatCompletionMessage();
		systemMessage.setRole(LLMRole.SYSTEM.getRole());
		systemMessage.setContent(systemPrompt);
		messageList.add(systemMessage);
		redisService.set(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey), GSON.toJson(messageList), 7, TimeUnit.DAYS);
		return ResponseModel.success(resultMap);
	}

	@Override
	public ResponseModel<Map<String, Object>> scoreForChat(String uniqueKey, String audioText, String duration, String id, String negativeScore) {
		Map<String, Object> result = new HashMap<>();
		result.put("id", id);
		// 1. 根据uniqueKey保存录音
//		try {
//			InputStream inputStream = file.getInputStream();
//			String originalFilename = file.getOriginalFilename();
//			String filePath = saveVoiceFile(uniqueKey, LLMRole.USER.getRole(), inputStream, originalFilename);
//			String recordAudioUrl = "https://demo.wangchaotech.com/smart_practice_audio/" + filePath;
//			result.put("recordAudioUrl", recordAudioUrl);
//		} catch (Exception ex) {
//			logger.error("录音文件保存错误，错误信息=", ex);
//			return ResponseModel.error(400, "录音文件保存错误");
//		}
//
//		// 2. 将录音调用语音识别接口，获取识别结果
//		String audioWord = null;
//		int negativeValue = 0; // 负面情感值默认0，取值范围是0-10
//		double wordTime = 60.0; // 暂时获取不到默认60秒
//		String resultJson = null;
//		try {
//			long s = System.currentTimeMillis();
//			resultJson = HttpClientUtils.postFile(asrUrl, null, file, 60000, 60000);
//			logger.info("语音识别结果{}，耗时：{}ms", resultJson, System.currentTimeMillis() - s);
//		} catch (Exception e) {
//			logger.error("AI请求失败，错误信息=", e);
//			return ResponseModel.error(400, "录音文件识别失败");
//		}
//		logger.info("返回值：{}", resultJson);
//		ResponseModel<ScoreContent> response = GSON.fromJson(resultJson, new TypeToken<ResponseModel<ScoreContent>>(){}.getType());
//		if (response != null) {
//			ScoreContent data = response.getData();
//			if (response.isSuccess() && data != null) {
//				audioWord = data.getText();
//				wordTime = data.getDuration();
//				negativeValue = (int) (data.getNegative_percent() * 100);
//			}
//		}
//
//		if (StringUtils.isBlank(audioWord)) {
//			return ResponseModel.error(ServiceConstant.ASR_ERROR_CODE, "语音转文本错误");
//		}
//
//		result.put("text", audioWord);


//		if (audioWord == null) {
//			audioWord = "";
//		}

		int negativeValue = 0; // 负面情感值默认0，取值范围是0-10
		if (StringUtils.isNotBlank(negativeScore)) {
			negativeValue = Integer.parseInt(negativeScore);
		}
		double wordTime = Double.parseDouble(duration); // 暂时获取不到默认60秒

		// 去除标点符号的纯文字文本
		String finalAudioWord = audioText.replaceAll("[\\pP\\pS\\pZ]", "");
		int wordCount = finalAudioWord.length();

		// 3. 获取评分的规则配置信息（从redis中获取）

		String judgeRulesStr = redisService.get("judge_rules");
		ArrayList<ScoreRule> scoreRuleList = new ArrayList<>();
		if (StringUtils.isNotBlank(judgeRulesStr)) {
			scoreRuleList = GSON.fromJson(judgeRulesStr, new TypeToken<ArrayList<ScoreRule>>() {
			}.getType());
		}

		ScoreRule speedRule = null;
		ScoreRule emotionRule = null;
		ScoreRule fluencyRule = null;
		ScoreRule keywordsRule = null;
		ScoreRule timeoutRule = null;
		ScoreRule relevanceRule = null;
		for (ScoreRule rule: scoreRuleList) {
			if (rule.getUniqueKey().equals(ServiceConstant.SCORE_RULE_SPEED)) {
				speedRule = rule;
			} else if (ServiceConstant.SCORE_RULE_FLUENCY.equals(rule.getUniqueKey())) {
				fluencyRule = rule;
			} else if (ServiceConstant.SCORE_RULE_EMOTION.equals(rule.getUniqueKey())) {
				emotionRule = rule;
			} else if (ServiceConstant.SCORE_RULE_KEYWORDS.equals(rule.getUniqueKey())) {
				keywordsRule = rule;
			} else if (ServiceConstant.SCORE_RULE_TIMEOUT.equals(rule.getUniqueKey())) {
				timeoutRule = rule;
			} else if (ServiceConstant.SCORE_RULE_RELEVANCE.equals(rule.getUniqueKey())) {
				relevanceRule = rule;
			}
		}

		// 4. 根据识别的文本结果对语速打分（工程自己判断，字数/录音时间(单位：分钟)），符合规则就得分
		if (speedRule != null && speedRule.isEnable()) {
			int minValue = speedRule.getMinValue();
			int maxValue = speedRule.getMaxValue();
			int score = speedRule.getWeight();
			double actualCount = (wordCount * 60) / wordTime;
			logger.info("语速的速度={}, 时间={}, 字数={}", actualCount, wordTime, wordCount);
			if (actualCount >= minValue && actualCount <= maxValue) {
				result.put("speedScore", score);
			} else {
				result.put("speedScore", 0);
			}
		} else {
			result.put("speedScore", 0);
		}

		if (keywordsRule != null && keywordsRule.isEnable()) {
			try {
				long s = System.currentTimeMillis();
//				String fluencyWordTemplate = "你是风险评估打分大师，以下用三个反引号围起来的内容是客服催收场景中客服说的话，请对客服话语的投诉风险进行打分，分数为1分~10分的整数，分数越高则风险越小，分数越低则越大。先输出原因后，再打分。以文本json格式输出，原因的key用reason，分数的key用score" + "```" + audioText + "```";
				String fluencyWordTemplate = "你是风险评估打分大师，将从一段催收对话的文本中判定其中存在的投诉风险。你将从对话中的客服用语和客户可能对客服语言的反应等方面判定。请注意，不影响整体对话的语气助词不在考虑范围内。以下用三个#围起来的内容是客服催收场景中客服说的话。请对客服话语的投诉风险进行打分，分数为0分~10分的整数，分数越高则风险越小，分数越低则越大。你需要向我展示出你所打出分数的分析，可以不展示分析过程，但是要明确原因。之后再给出你所判定的分数。如果客服的对话内容是在和客户打招呼、问候以及跟客户友好的终止对话的话术，则认为是高度相关。在给出评分之前，请先提供评分的原因。原因不超过100个字符。以文本json格式输出，原因的key用reason，分数的key用score。" + "###" + audioText + "###";
				List<ChatCompletionMessage> fluencyMessageList = new ArrayList<>();
				ChatCompletionMessage fluencyMessage = new ChatCompletionMessage();
				fluencyMessage.setContent(fluencyWordTemplate);
				fluencyMessage.setRole(LLMRole.USER.getRole());
				fluencyMessageList.add(fluencyMessage);
				String fluencyResultJson = requestChatModel(fluencyMessageList, false);
				ChatCompletionResponse fluencyResponse = GSON.fromJson(fluencyResultJson, ChatCompletionResponse.class);
				if (fluencyResponse != null) {
					List<ChatCompletionChoice> choices = fluencyResponse.getChoices();
					if (choices != null && !choices.isEmpty() && choices.getFirst().getMessage() != null && choices.getFirst().getMessage().getContent() != null) {
						logger.info("投诉风险模型结果：{}", choices.getFirst().getMessage().getContent());
						String json = handleAiJsonResult(choices.getFirst().getMessage().getContent());
						ScoreLLMResponse scoreLLMResponse = GSON.fromJson(json, ScoreLLMResponse.class);
						int score = scoreLLMResponse.getScore();
						logger.info("投诉风险分数={}", score);
						int percent = (int) (((double) score / 10) * 100);
						if (percent >= keywordsRule.getMaxValue()) {
							// 大于最大值，不扣分
							result.put("keywordScore", keywordsRule.getWeight());
						} else if (percent >= keywordsRule.getMinValue() && percent <= keywordsRule.getMaxValue()) {
							// 计算需要扣多少分
							int deductScore = (int) (keywordsRule.getWeight() * ((double) percent / 100));
							result.put("keywordScore", deductScore);
						} else {
							result.put("keywordScore", 0);
						}
					}
				}
				logger.info("投诉风险耗时: {}ms", (System.currentTimeMillis() - s));
			} catch (Exception ex) {
				logger.error("投诉风险处理失败, 错误信息=", ex);
				result.put("keywordScore", 0);
			}
		}


		// 5. 根据识别的文本结果对话术流畅度、话题相关性打分（调用大模型接口）
		if (fluencyRule != null && fluencyRule.isEnable()) {
			try {
				long s = System.currentTimeMillis();
//				String fluencyWordTemplate = "流畅度评分任务：作为专业的语言流畅度评估师，您将对客服催收场景中的客服话语进行评分。请遵循以下评分准则：0-3分：语言极不流畅，存在显著的沟通障碍或语法错误。4-6分：语言较为生硬，可能包含一些不自然或重复的表达方式。7-10分：语言极为流畅，表达自然、清晰，易于听众理解。在给出评分之前，请先提供评分的原因。原因不超过100个字符。评分完成后，请以JSON格式提供您的评估结果，包括以下两个关键信息：reason（原因）：详细解释您给出该分数的原因。score（分数）：您给出的分数，范围在0到10之间。请开始对以下三个反引号围起来的内容做评估。" + "```" + audioText + "```";
				String fluencyWordTemplate = "流畅度评分任务：作为专业的语言流畅度评估师，您将对客服催收场景中的客服话语进行评分。请以JSON格式提供您的评估结果，包括score（分数）：您给出的分数，范围在0到10之间。不要输出推理过程。请开始对以下三个反引号围起来的内容做评估。" + "```" + audioText + "```";
				List<ChatCompletionMessage> fluencyMessageList = new ArrayList<>();
				ChatCompletionMessage fluencyMessage = new ChatCompletionMessage();
				fluencyMessage.setContent(fluencyWordTemplate);
				fluencyMessage.setRole(LLMRole.USER.getRole());
				fluencyMessageList.add(fluencyMessage);
				String fluencyResultJson = requestChatModel(fluencyMessageList, false);
				ChatCompletionResponse fluencyResponse = GSON.fromJson(fluencyResultJson, ChatCompletionResponse.class);
				if (fluencyResponse != null) {
					List<ChatCompletionChoice> choices = fluencyResponse.getChoices();
					if (choices != null && !choices.isEmpty() && choices.getFirst().getMessage() != null && choices.getFirst().getMessage().getContent() != null) {
						logger.info("流畅度模型结果：{}", choices.getFirst().getMessage().getContent());
						String json = handleAiJsonResult(choices.getFirst().getMessage().getContent());
						ScoreLLMResponse scoreLLMResponse = GSON.fromJson(json, ScoreLLMResponse.class);
						int score = scoreLLMResponse.getScore();
						logger.info("流畅度分数={}", score);
						int percent = (int) (((double) score / 10) * 100);
						if (percent >= fluencyRule.getMaxValue()) {
							// 大于最大值，不扣分
							result.put("fluencyScore", fluencyRule.getWeight());
						} else if (percent >= fluencyRule.getMinValue() && percent <= fluencyRule.getMaxValue()) {
							// 计算需要扣多少分
							int deductScore = (int)(fluencyRule.getWeight() * ((double) percent / 100));
							result.put("fluencyScore", deductScore);
						} else {
							result.put("fluencyScore", 0);
						}
					}
				}
				logger.info("话术流畅度耗时: {}ms", (System.currentTimeMillis() - s));
			} catch (Exception ex) {
				logger.error("对话术流畅度处理失败, 错误信息=", ex);
				result.put("fluencyScore", 0);
			}
		} else {
			result.put("fluencyScore", 0);
		}

		// 话题相关性
		result.put("correlationScore", 0);
		if (relevanceRule != null && relevanceRule.isEnable()) {
			try {
				long s = System.currentTimeMillis();
				String correlationWordTemplate = "你将作为客服对话关联度评估员。你的任务是阅读以下客服对话，并根据对话内容与特定主题的关联程度进行评分。可以从以下几个维度思考关联度：\"对话内容是否直接涉及或暗示了主题？\",\"对话是否深入探讨了主题相关的问题或解决方案？\", \"对话是否清晰表达了与主题相关的信息？\",\"对话内容是否适合主题，没有偏离主题？\n对话主题是：催收贷款\n以下用三个反引号围起来的内容是客服催收场景中客服说的话，请对客服话语与主题的相关性行打分，请使用0到10的评分系统，其中0表示完全不相关，10表示高度相关。如果客服的对话内容是在和客户打招呼、问候以及跟客户友好的终止对话的话术，则认为是高度相关。在给出评分之前，请先提供评分的原因。原因不超过100个字符。以文本json格式输出，原因的key用reason，分数的key用score。" + "```" + audioText + "```";
				List<ChatCompletionMessage> correlationMessageList = new ArrayList<>();
				ChatCompletionMessage correlationMessage = new ChatCompletionMessage();
				correlationMessage.setContent(correlationWordTemplate);
				correlationMessage.setRole(LLMRole.USER.getRole());
				correlationMessageList.add(correlationMessage);
				String correlationResultJson = requestChatModel(correlationMessageList, false);
				ChatCompletionResponse correlationResponse = GSON.fromJson(correlationResultJson, ChatCompletionResponse.class);
				if (correlationResponse != null) {
					List<ChatCompletionChoice> choices = correlationResponse.getChoices();
					if (choices != null && !choices.isEmpty() && choices.getFirst().getMessage() != null && choices.getFirst().getMessage().getContent() != null) {
						logger.info("话题相关性结果：{}", choices.getFirst().getMessage().getContent());
						String json = handleAiJsonResult(choices.getFirst().getMessage().getContent());
						ScoreLLMResponse scoreLLMResponse = GSON.fromJson(json, ScoreLLMResponse.class);
						int score = scoreLLMResponse.getScore();
						logger.info("话题相关性分数={}", score);
						int percent = (int) (((double) score / 10) * 100);
						if (percent >= relevanceRule.getMaxValue()) {
							// 大于最大值，不扣分
							result.put("correlationScore", relevanceRule.getWeight());
						} else if (percent >= relevanceRule.getMinValue() && percent <= relevanceRule.getMaxValue()) {
							// 计算需要扣多少分
							int deductScore = (int)(relevanceRule.getWeight() * ((double) percent / 100));
							result.put("correlationScore", deductScore);
						} else {
							result.put("correlationScore", 0);
						}
					}
				}
				logger.info("话题相关性耗时: {}ms", (System.currentTimeMillis() - s));
			} catch (Exception ex) {
				logger.error("话题相关性处理失败，错误信息=", ex);
				result.put("correlationScore", 0);
			}
		} else {
			result.put("correlationScore", 0);
		}

		// 情绪识别
		if (emotionRule != null && emotionRule.isEnable()) {
			int emotionScore = emotionRule.getWeight();
			if (negativeValue <= emotionRule.getMaxValue()) {
				result.put("emotionScore", emotionScore);
			} else {
				result.put("emotionScore", 0);
			}
		} else {
			result.put("emotionScore", 0);
		}

//		// 保存客服的对话
//		String mockChatInfoStr = redisService.get(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey));
//		List<ChatCompletionMessage> messageList = GSON.fromJson(mockChatInfoStr, new TypeToken<List<ChatCompletionMessage>>(){}.getType());
//		ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage();
//		chatCompletionMessage.setContent(audioText);
//		chatCompletionMessage.setRole(LLMRole.USER.getRole());
//		messageList.add(chatCompletionMessage);
//		redisService.set(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey), GSON.toJson(messageList), 7, TimeUnit.HOURS);

		// 将每一轮对话的内容的客服的分数保存起来
		String chatScoreListJson = redisService.get(String.format(ServiceConstant.MOCK_CHAT_SCORE_KEY, uniqueKey));
		List<Map<String, Object>> scoreList = new ArrayList<>();
		if (StringUtils.isNotBlank(chatScoreListJson)) {
			scoreList = GSON.fromJson(chatScoreListJson, new TypeToken<List<Map<String, Object>>>(){}.getType());
		}
		scoreList.add(result);
		redisService.set(String.format(ServiceConstant.MOCK_CHAT_SCORE_KEY, uniqueKey), GSON.toJson(scoreList), 7, TimeUnit.DAYS);

		return ResponseModel.success(result);
	}

	private String handleAiJsonResult(String originalJson) {
		logger.info("处理之前的JSON={}", originalJson);
		originalJson = originalJson.replaceAll("\n", "").replaceAll(" ", "");
		int jsonStart = originalJson.lastIndexOf("```json");
		int jsonEnd = originalJson.lastIndexOf("```");
		String json = originalJson;
		if (jsonStart != -1 && jsonEnd != -1 && jsonStart != jsonEnd) {
			json = originalJson.substring(jsonStart + 7, jsonEnd);
		} else {
			json = json.replaceAll("```", "");
			if (!json.startsWith("{") || !json.endsWith("}")) {
				int _start = json.lastIndexOf("{");
				int _end = json.lastIndexOf("}");
				if (_start < _end) {
					json = json.substring(_start, _end + 1);
				}
			}
		}
		logger.info("处理之后的JSON={}", json);
		return json;
	}

	@Override
	public ResponseModel<Map<String, Object>> audioToText(String uniqueKey, MultipartFile file) {

		Map<String, Object> result = new HashMap<>();
		// 1. 根据uniqueKey保存录音
		try {
			InputStream inputStream = file.getInputStream();
			String originalFilename = file.getOriginalFilename();
			String filePath = saveVoiceFile(uniqueKey, LLMRole.USER.getRole(), inputStream, originalFilename);
			String recordAudioUrl = "https://demo.wangchaotech.com/smart_practice_audio/" + filePath;
			result.put("recordAudioUrl", recordAudioUrl);
		} catch (Exception ex) {
			logger.error("录音文件保存错误，错误信息=", ex);
			return ResponseModel.error(400, "录音文件保存错误");
		}

		// 2. 将录音调用语音识别接口，获取识别结果
		String audioWord = null;
		int negativeValue = 0; // 负面情感值默认0，取值范围是0-10
		double duration = 60.0; // 暂时获取不到默认60秒
		String resultJson = null;
		try {
			long s = System.currentTimeMillis();
			resultJson = HttpClientUtils.postFile(asrUrl, null, file, 60000, 60000);
			logger.info("语音识别结果{}，耗时：{}ms", resultJson, System.currentTimeMillis() - s);
		} catch (Exception e) {
			logger.error("AI请求失败，错误信息=", e);
			return ResponseModel.error(400, "录音文件识别失败");
		}
		logger.info("返回值：{}", resultJson);
		ResponseModel<ScoreContent> response = GSON.fromJson(resultJson, new TypeToken<ResponseModel<ScoreContent>>(){}.getType());
		if (response != null) {
			ScoreContent data = response.getData();
			if (response.isSuccess() && data != null) {
				audioWord = data.getText();
				duration = data.getDuration();
				negativeValue = (int) (data.getNegative_percent() * 100);
			}
		}
		if (StringUtils.isBlank(audioWord)) {
			return ResponseModel.error(ServiceConstant.ASR_ERROR_CODE, "语音转文本错误");
		}
		result.put("text", audioWord);
		result.put("duration", duration);
		result.put("emotionScore", negativeValue);

		// 保存客服的对话
		String mockChatInfoStr = redisService.get(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey));
		List<ChatCompletionMessage> messageList = GSON.fromJson(mockChatInfoStr, new TypeToken<List<ChatCompletionMessage>>(){}.getType());
		ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage();
		chatCompletionMessage.setContent(audioWord);
		chatCompletionMessage.setRole(LLMRole.USER.getRole());
		messageList.add(chatCompletionMessage);
		redisService.set(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey), GSON.toJson(messageList), 7, TimeUnit.DAYS);

		return ResponseModel.success(result);
	}


	/**
	 * 保存文件并返回可访问的URL
	 * @param uniqueKey 唯一标识
	 * @param role 角色
	 * @param fileStream 文件
	 * @return 可访问的文件URL
	 */
	public String saveVoiceFile(String uniqueKey, String role, InputStream fileStream,String originalFileName) {

		// 提取文件后缀
		String fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
		// 相对路径，确保与Nginx的配置相匹配
		String relativePath = Paths.get(saveVoicePath, "voice", uniqueKey).toString();
		File savePath = new File(relativePath);
		if (!savePath.exists()) {
			savePath.mkdirs();
		}
		// 构建完整文件名
		String newFilename =  System.currentTimeMillis() + fileExtension;
		File targetFile = new File(savePath, newFilename);

		// 将InputStream的内容写入文件
		try (FileOutputStream out = new FileOutputStream(targetFile)) {
			byte[] buffer = new byte[1024];
			int bytesRead;
			while ((bytesRead = fileStream.read(buffer)) != -1) {
				out.write(buffer, 0, bytesRead);
			}
		} catch (IOException e) {
			logger.error("Error saving file: {}", e.getMessage(), e);
			return "Error saving file: " + e.getMessage();
		}
		// 返回修改后的可访问URL
        return uniqueKey + "/" + targetFile.getName();
	}

	/**
	 * 获取文件夹下所有的文件并合成一个文件
	 */
	@Override
	public String compoundSavedFile(String uniqueKey) {
		//获取uniqueKey文件夹下所有文件
		String relativePath = Paths.get(saveVoicePath, "voice", uniqueKey).toString();
		File directory = new File(relativePath);

		File[] files = directory.listFiles((dir, name) -> name.endsWith(".wav")&& !name.equals("combined.wav"));
		if (files == null || files.length == 0) {
			logger.error("{}文件下没有合成文件",uniqueKey);
			return null;
		}
		// 按文件名排序（假设文件名反映时间戳）
		Arrays.sort(files, Comparator.comparingLong(file -> Long.parseLong(file.getName().replace(".wav", ""))));
		// 目标格式，统一格式为 PCM_SIGNED, 16位, 44100 Hz, 单声道
		AudioFormat targetFormat = new AudioFormat(
				AudioFormat.Encoding.PCM_SIGNED,
				44100,
				16,
				1,
				2,
				44100,
				false
		);

		try {

			// 合并音频文件
			List<AudioInputStream> audioInputStreams = new ArrayList<>();
			for (File audioFile : files) {
				AudioInputStream originalStream = AudioSystem.getAudioInputStream(audioFile);
				AudioInputStream convertedStream = AudioSystem.getAudioInputStream(targetFormat, originalStream);
				audioInputStreams.add(convertedStream);
			}

			AudioInputStream concatenatedStream = new SequenceAudioInputStream(targetFormat, audioInputStreams);
			// 输出合成后的文件
			File outputFile = new File(directory, "combined.wav");
			if (outputFile.exists()) {
				outputFile.delete(); // 明确删除现有文件
			}
			AudioSystem.write(concatenatedStream, AudioFileFormat.Type.WAVE, outputFile);
			logger.info("合成文件= {}" ,outputFile.getAbsolutePath());
			return uniqueKey + "/" + "combined.wav";
		}  catch (IOException | UnsupportedAudioFileException e) {
            logger.error("合成文件失败= {}", e.getMessage());
			throw new RuntimeException(e);
		}

	}


	//请求大模型接口
	public String requestChatModel(List<ChatCompletionMessage> messageList,boolean stream) {
		Map<String, String> header=new HashMap<>();
//		header.put(ServiceConstant.MOCK_REQUEST_AUTHOR_PARAM, aiModelLlmToken);
		// 2. 合并前端传来的prompt到ChatCompletionRequest的message中
		ChatCompletionRequest request = new ChatCompletionRequest();
		request.setModel(chatModelName);
		request.setMessages(messageList);
		request.setStream(stream);
		String url = aiModelLlmUrl + "/v1/chat/completions";
		String resultJson = null;
		String reqParams = GSON.toJson(request);
		try {
			resultJson = HttpClientUtils.postJson(url, header, reqParams, 60000, 60000, 60000);
		} catch (Exception e) {
			logger.error("AI请求失败，错误信息=", e);
		}
		logger.info("【{}】请求参数: {}", url,reqParams);
		logger.info("返回值：{}", resultJson);
		return resultJson;
	}

	@Override
	public ResponseModel<Map<String, Object>> scoreForAllChat(String uniqueKey) {
		Map<String, Object> result = new HashMap<>();
		// 1. 计算所有轮次的分数,求取一个平均值
		String chatScoreListJson = redisService.get(String.format(ServiceConstant.MOCK_CHAT_SCORE_KEY, uniqueKey));
		List<ScoreInfo> scoreList = new ArrayList<>();
		if (StringUtils.isNotBlank(chatScoreListJson)) {
			scoreList = GSON.fromJson(chatScoreListJson, new TypeToken<List<ScoreInfo>>(){}.getType());
		}
		int speedScore = 0;
		int emotionScore = 0;
		int fluencyScore = 0;
		int correlationScore = 0;
		int keywordScore = 0;
		for (ScoreInfo scoreInfo : scoreList) {
			speedScore += scoreInfo.getSpeedScore();
			emotionScore += scoreInfo.getEmotionScore();
			fluencyScore += scoreInfo.getFluencyScore();
			correlationScore += scoreInfo.getCorrelationScore();
			keywordScore += scoreInfo.getKeywordScore();
		}
		if (!scoreList.isEmpty()) {
			result.put("speedScore", speedScore / scoreList.size());
			result.put("emotionScore", emotionScore / scoreList.size());
			result.put("fluencyScore", fluencyScore / scoreList.size());
			result.put("correlationScore", correlationScore / scoreList.size());
			result.put("keywordScore", keywordScore / scoreList.size());
		}

		// 2. 关键词匹配做评估
//		StringBuilder audioWordBuilder = new StringBuilder();
//		String mockChatInfoStr = redisService.get(String.format(ServiceConstant.MOCK_CHAT_UNIQUE_KEY, uniqueKey));
//		List<ChatCompletionMessage> messageList = new ArrayList<>();
//		if (StringUtils.isNotBlank(mockChatInfoStr)) {
//			messageList = GSON.fromJson(mockChatInfoStr, new TypeToken<List<ChatCompletionMessage>>(){}.getType());
//		}
//		messageList = messageList.stream().filter(message -> "user".equals(message.getRole())).toList();
//		for (ChatCompletionMessage chatCompletionMessage : messageList) {
//			audioWordBuilder.append(chatCompletionMessage.getContent()).append("。");
//		}
//		String audioWord = audioWordBuilder.toString();
//
//		HashSet<String> keywordSet = new HashSet<>();
//		String chatKeywordScoreConfigJson = redisService.get("judge_keywords");
//		List<String> keywordScoreConfigList = new ArrayList<>();
//		if (StringUtils.isNotBlank(chatKeywordScoreConfigJson)) {
//			keywordScoreConfigList = GSON.fromJson(chatKeywordScoreConfigJson, new TypeToken<List<String>>() {
//			}.getType());
//		}
//		for (String keyword : keywordScoreConfigList) {
//			if (audioWord.contains(keyword)) {
//				keywordSet.add(keyword);
//			}
//		}
//		int keywordTimes = keywordSet.size();
//
//		// 获取到关键词的打分规则
//		String judgeRulesStr = redisService.get("judge_rules");
//		ArrayList<ScoreRule> scoreRuleList = new ArrayList<>();
//		if (StringUtils.isNotBlank(judgeRulesStr)) {
//			scoreRuleList = GSON.fromJson(judgeRulesStr, new TypeToken<ArrayList<ScoreRule>>() {
//			}.getType());
//		}
//		List<ScoreRule> keywordsRules = scoreRuleList.stream().filter(rule -> ServiceConstant.SCORE_RULE_KEYWORDS.equals(rule.getUniqueKey())).toList();
//		if (!keywordsRules.isEmpty() && !keywordScoreConfigList.isEmpty()) {
//			ScoreRule keywordsRule = keywordsRules.getFirst();
//			if (keywordsRule != null && keywordsRule.isEnable()) {
//				// 获取关键词的占比
//				double keywordPercent = Math.round(100.0 * ((keywordTimes * 100.0) / keywordScoreConfigList.size())) / 100.0;
//				if (keywordPercent == keywordsRule.getMaxValue()) {
//					result.put("keywordScore", 0);
//				} else if (keywordPercent >= keywordsRule.getMinValue() && keywordPercent < keywordsRule.getMaxValue()) {
//					result.put("keywordScore", (int) (keywordsRule.getWeight() * (100 - keywordPercent) / 100));
//				} else {
//					result.put("keywordScore", keywordsRule.getWeight());
//				}
//			} else {
//				result.put("keywordScore", 0);
//			}
//		} else {
//			result.put("keywordScore", 0);
//		}


		// 3. AI给出的建议
		// 4. 将所有的录音进行合成
		String compoundFilePath = compoundSavedFile(uniqueKey);
		String compoundAudioUrl = "https://demo.wangchaotech.com/smart_practice_audio/" + compoundFilePath;
		result.put("compoundAudioUrl", compoundAudioUrl);
		return ResponseModel.success(result);
	}

	// 客户信息
	static class CustomerInfo {
		private String name;	// 姓名
		private String gender;	// 性别
		private String address;	// 家庭地址
		private Integer overDueDays;	// 逾期天数
		private String owedAmount;	// 欠款金额
		private String job;		// 工作单位

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getGender() {
			return gender;
		}

		public void setGender(String gender) {
			this.gender = gender;
		}

		public String getAddress() {
			return address;
		}

		public void setAddress(String address) {
			this.address = address;
		}

		public Integer getOverDueDays() {
			return overDueDays;
		}

		public void setOverDueDays(Integer overDueDays) {
			this.overDueDays = overDueDays;
		}

		public String getOwedAmount() {
			return owedAmount;
		}

		public void setOwedAmount(String owedAmount) {
			this.owedAmount = owedAmount;
		}

		public String getJob() {
			return job;
		}

		public void setJob(String job) {
			this.job = job;
		}
	}

	public String getCustomerRole() {
		// 从 Redis 获取角色列表
		String roleListStr = redisService.get("MOCK_ROLE_LIST");
		if (roleListStr == null || roleListStr.isEmpty()) {
			logger.warn("Redis 中未找到 MOCK_ROLE_LIST 或值为空,从getLlmRole获取");
			roleListStr = getLlmRole();
			return "默认角色设定\n注意：你回复我的字数不超过100字。";
		}

		// 解析 JSON 字符串为 List<String>
		List<String> roleSystemPromptList = GSON.fromJson(roleListStr, new TypeToken<List<String>>() {}.getType());
		if (roleSystemPromptList == null || roleSystemPromptList.isEmpty()) {
			logger.warn("MOCK_ROLE_LIST 解析为空列表或格式不正确");
			return "默认角色设定\n注意：你回复我的字数不超过100字。";
		}

		// 随机选择一个角色
		int index = new Random().nextInt(roleSystemPromptList.size());
		logger.info("选中的 index={}, 角色设定内容={}", index, roleSystemPromptList.get(index));
		String mainPrompt = roleSystemPromptList.get(index);
		String limitPrompt = "注意：你回复我的字数不超过100字。";
		return mainPrompt + "\n" + limitPrompt;
	}

	private static final List<String> ROLE_TEMPLATES = Arrays.asList(
			"### 足部健康咨询\n" +
					"**角色**：\n" +
					"- 专家：足科医生，提供诊断建议\n" +
					"- 用户：有足部问题的患者\n\n" +

					"**咨询要点**：\n" +
					"1. 描述症状（部位/时长/疼痛类型）\n" +
					"2. 医生给出专业建议\n" +
					"3. 提供日常护理方法\n\n" +

					"**常见问题**：\n" +
					"- 足跟痛：可能足底筋膜炎，建议拉伸+冰敷\n" +
					"- 脚趾变形：可能拇外翻，建议宽头鞋\n" +
					"- 脚底硬茧：不要自己切割，定期护理\n" +
					"- 运动水泡：检查鞋袜是否合适\n" +
					"- 糖尿病足：需特别护理，定期检查\n\n" +

					"**对话示例**：\n" +
					"患者：早上走路脚后跟特别疼\n" +
					"医生：可能是足底筋膜炎，建议：\n" +
					"1. 换支撑好的鞋子\n" +
					"2. 每天做足底拉伸\n" +
					"3. 疼痛时冰敷15分钟"
	);


	public String getLlmRole() {
		// 随机选择一个角色模板
		int index = new Random().nextInt(ROLE_TEMPLATES.size());
		String mainPrompt = ROLE_TEMPLATES.get(index);
		String limitPrompt = "注意：你回复我的字数不超过100字,不要带上额外的语气描述词，不要带括号。";
		return mainPrompt + "\n" + limitPrompt;
	}


	@Override
	public ResponseModel<String> exportTestResult(String date) {
		// 设置要搜索的目录
		File directory = new File("/root/servers/smart-practice/voice");
//		File directory = new File("D:\\workspace\\hjzd\\git_code\\smart-practice\\voice");
		// 设置开始和结束日期
		Date startDate;
		Date endDate;
		try {
			startDate = DateUtils.parseDate(date + " 00:00:00", "yyyy-MM-dd HH:mm:ss");
			endDate = DateUtils.parseDate(date + " 23:59:59", "yyyy-MM-dd HH:mm:ss");
		} catch (ParseException e) {
			return ResponseModel.error(ServiceConstant.EXCEPTION, "时间转换错误");
		}
		// 遍历目录
		File[] directories = directory.listFiles(new FileFilter() {
			@Override
			public boolean accept(File file) {
				return file.isDirectory() && file.lastModified() >= startDate.getTime()
						&& file.lastModified() < endDate.getTime();
			}
		});

		if (directories == null) {
			return null;
		}

		List<File> files = Arrays.asList(directories);
		files.sort((o1, o2) -> (int) (o1.lastModified() - o2.lastModified()));

		Type messageListType = new TypeToken<List<ChatCompletionMessage>>() {
		}.getType();
		Type scoreListType = new TypeToken<List<ScoreInfo>>() {
		}.getType();
		List<TestResultExcel> excelList = new ArrayList<>();
		for (File dir : files) {
			String uniqueKey = dir.getName();
			String chatMessageListStr = redisService.get("MOCK_CHAT_UNIQUE_" + uniqueKey);    // 对话列表
			String scoreListStr = redisService.get("MOCK_CHAT_SCORE_" + uniqueKey);    // 得分列表
			if (StringUtils.isBlank(chatMessageListStr) || StringUtils.isBlank(scoreListStr)) {
				logger.info(">>> 信息不存在了，uniqueKey={}", uniqueKey);
				continue;
			}

			TestResultExcel excel = new TestResultExcel();
			excelList.add(excel);

			excel.setTestTime(DateFormatUtils.format(new Date(dir.lastModified()), "yyyy-MM-dd HH:mm:ss"));

			//
			excel.setFluencyPrompt("流畅度评分任务：作为专业的语言流畅度评估师，您将对客服催收场景中的客服话语进行评分。请遵循以下评分准则：0-3分：语言极不流畅，存在显著的沟通障碍或语法错误。4-6分：语言较为生硬，可能包含一些不自然或重复的表达方式。7-10分：语言极为流畅，表达自然、清晰，易于听众理解。在给出评分之前，请先提供评分的原因。原因不超过100个字符。评分完成后，请以JSON格式提供您的评估结果，包括以下两个关键信息：reason（原因）：详细解释您给出该分数的原因。score（分数）：您给出的分数，范围在0到10之间。请开始对以下三个反引号围起来的内容做评估。" + "```<对话内容>```");
			excel.setKeywordPrompt("你是风险评估打分大师，将从一段催收对话的文本中判定其中存在的投诉风险。你将从对话中的客服用语和客户可能对客服语言的反应等方面判定。请注意，不影响整体对话的语气助词不在考虑范围内。以下用三个#围起来的内容是客服催收场景中客服说的话。请对客服话语的投诉风险进行打分，分数为0分~10分的整数，分数越高则风险越小，分数越低则越大。你需要向我展示出你所打出分数的分析，可以不展示分析过程，但是要明确原因。之后再给出你所判定的分数。如果客服的对话内容是在和客户打招呼、问候以及跟客户友好的终止对话的话术，则认为是高度相关。在给出评分之前，请先提供评分的原因。原因不超过100个字符。以文本json格式输出，原因的key用reason，分数的key用score。" + "###<对话内容>###");
			excel.setCorrelationPrompt("你将作为客服对话关联度评估员。你的任务是阅读以下客服对话，并根据对话内容与特定主题的关联程度进行评分。可以从以下几个维度思考关联度：\"对话内容是否直接涉及或暗示了主题？\",\"对话是否深入探讨了主题相关的问题或解决方案？\", \"对话是否清晰表达了与主题相关的信息？\",\"对话内容是否适合主题，没有偏离主题？\n对话主题是：催收贷款\n以下用三个反引号围起来的内容是客服催收场景中客服说的话，请对客服话语与主题的相关性行打分，请使用0到10的评分系统，其中0表示完全不相关，10表示高度相关。如果客服的对话内容是在和客户打招呼、问候以及跟客户友好的终止对话的话术，则认为是高度相关。在给出评分之前，请先提供评分的原因。原因不超过100个字符。以文本json格式输出，原因的key用reason，分数的key用score。" + "```<对话内容>```");

			List<ChatCompletionMessage> chatCompletionMessageList = GSON.fromJson(chatMessageListStr, messageListType);
			List<ScoreInfo> scoreInfoList = GSON.fromJson(scoreListStr, scoreListType);
			String rolePrompt = chatCompletionMessageList.get(0).getContent();    // 角色设定prompt
			excel.setRolePrompt(rolePrompt);
			int index = 0;
			StringBuilder dialogue = new StringBuilder();
			for (ChatCompletionMessage message : chatCompletionMessageList) {
				if (message.getRole().equals(LLMRole.USER.getRole())) {
					// 客服，需要匹配打分
					dialogue.append("客服：").append(message.getContent()).append("\n");
					try {
						ScoreInfo scoreInfo = scoreInfoList.get(index);
						dialogue.append("语速检测得分：").append(scoreInfo.getSpeedScore()).append("\n");
						dialogue.append("情绪识别得分：").append(scoreInfo.getEmotionScore()).append("\n");
						dialogue.append("话术相关性得分：").append(scoreInfo.getCorrelationScore()).append("\n");
						dialogue.append("对话流畅度得分：").append(scoreInfo.getFluencyScore()).append("\n");
						dialogue.append("投诉风险得分：").append(scoreInfo.getKeywordScore()).append("\n");
					} catch (Exception ex) {
						logger.error(">>> 打分数据有问题，scoreInfoList={}", GSON.toJson(scoreInfoList));
					}
					index++;
				} else if (message.getRole().equals(LLMRole.ASSISTANT.getRole())) {
					dialogue.append("客户：").append(message.getContent()).append("\n");
				}
			}
			excel.setDialogue(dialogue.toString());
			excel.setAiSuggestPrompt("下面三个反引号里面的内容是一段电话催收场景中客户与客服的对话，\n```" + dialogue + "```\n请从语速检测、情绪识别、话术相关性、对话流畅度、投诉风险的维度给出建议，每个维度的建议不超过30个字。返回的示例参考三个#括起来的内容。\n" +
					"###\n" +
					"1、语速检测：建议客服保持稳定语速，确保客户理解信息，避免增加客户压力\n" +
					"2、情绪识别：建议客服培训情绪识别技巧，对客户情绪波动敏感，适时安抚。\n" +
					"3、话术相关性：建议调整话术，更多关注客户实际困难，提供实际帮助或延期方案，减少催收压力。\n" +
					"4、对话流畅度：建议客服使用开放式问题，鼓励客户表达，同时保持对话连贯，避免打断客户。\n" +
					"5、投诉风险：建议定期回顾催收策略，确保遵守法律法规，避免过度催收，减少客户投诉风险。\n" +
					"###");
			excel.setAiSuggestion(redisService.get("MOCK_CHAT_AI_SUGGESTION_" + uniqueKey));
			excel.setAudioUrl("https://demo.wangchaotech.com/smart_practice_audio/" + uniqueKey + "/combined.wav");


			int speedScore = 0;
			int emotionScore = 0;
			int fluencyScore = 0;
			int correlationScore = 0;
			int keywordScore = 0;
			StringBuilder totalScoreSb = new StringBuilder();
			for (ScoreInfo scoreInfo : scoreInfoList) {
				speedScore += scoreInfo.getSpeedScore();
				emotionScore += scoreInfo.getEmotionScore();
				fluencyScore += scoreInfo.getFluencyScore();
				correlationScore += scoreInfo.getCorrelationScore();
				keywordScore += scoreInfo.getKeywordScore();
			}
			speedScore = speedScore / scoreInfoList.size();
			emotionScore = emotionScore / scoreInfoList.size();
			fluencyScore = fluencyScore / scoreInfoList.size();
			correlationScore = correlationScore / scoreInfoList.size();
			keywordScore = keywordScore / scoreInfoList.size();

			totalScoreSb.append("总分：").append(speedScore + emotionScore + fluencyScore + keywordScore + correlationScore).append("\n");
			totalScoreSb.append("语速检测扣分：").append(speedScore).append("\n");
			totalScoreSb.append("情绪识别扣分：").append(emotionScore).append("\n");
			totalScoreSb.append("话术相关性扣分：").append(correlationScore).append("\n");
			totalScoreSb.append("对话流畅度扣分：").append(fluencyScore).append("\n");
			totalScoreSb.append("投诉风险扣分：").append(keywordScore).append("\n");
			excel.setTotalScore(totalScoreSb.toString());
		}
		String fileName = "测试结果_" + date + ".xlsx";
		try (ExcelWriter excelWriter = EasyExcel.write("./result/" + fileName, TestResultExcel.class).build()) {
			// 这里注意 如果同一个sheet只要创建一次
			WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build();
			int pageSize = 1000;
			List<TestResultExcel> newList = new ArrayList<>();
			for (TestResultExcel caseAnalysisExcel : excelList) {
				newList.add(caseAnalysisExcel);
				if (newList.size() >= pageSize) {
					excelWriter.write(newList, writeSheet);
					newList.clear();
				}
			}
			// 后期数据量大的话，需要内存分页后写入
			if (!newList.isEmpty()) {
				excelWriter.write(newList, writeSheet);
			}
		}
		return ResponseModel.success("https://demo.wangchaotech.com/smart_practice_result/" + fileName);
	}

	@Override
	public ResponseModel<List<String>> getCharacters() {
		try {
			String resultJson = HttpClientUtils.doGet("http://10.253.50.31:5002/v1/chat/characters");
			Map<String, List<String>> resultMap = GSON.fromJson(resultJson, new TypeToken<Map<String, List<String>>>() {
			}.getType());
			return ResponseModel.success(resultMap.get("characters"));
		} catch (Exception e) {
			logger.error(">>> 获取角色列表失败，错误信息=", e);
		}
		return ResponseModel.success(new ArrayList<>());
	}

	class ScoreInfo {
		private int emotionScore;
		private int speedScore;
		private int correlationScore;
		private int keywordScore;
		private int fluencyScore;

		public int getEmotionScore() {
			return emotionScore;
		}

		public void setEmotionScore(int emotionScore) {
			this.emotionScore = emotionScore;
		}

		public int getSpeedScore() {
			return speedScore;
		}

		public void setSpeedScore(int speedScore) {
			this.speedScore = speedScore;
		}

		public int getCorrelationScore() {
			return correlationScore;
		}

		public void setCorrelationScore(int correlationScore) {
			this.correlationScore = correlationScore;
		}

		public int getKeywordScore() {
			return keywordScore;
		}

		public void setKeywordScore(int keywordScore) {
			this.keywordScore = keywordScore;
		}

		public int getFluencyScore() {
			return fluencyScore;
		}

		public void setFluencyScore(int fluencyScore) {
			this.fluencyScore = fluencyScore;
		}
	}

	public String generateCustomerPortraits() {
		List<List<String>> importantOppositeFlags = new ArrayList<>();
		importantOppositeFlags.add(Arrays.asList("你没有还款意愿。", "你有还款意愿。"));
		importantOppositeFlags.add(Arrays.asList("你同意仅结清本金的方案。", "你不同意仅结清本金的方案。"));
		importantOppositeFlags.add(Arrays.asList("你愿意以分期的方式还款。", "你不愿意以分期的方式还款。"));
		importantOppositeFlags.add(Arrays.asList("你同意添加微信。", "你不同意添加微信。"));
		importantOppositeFlags.add(Arrays.asList("你和我约定会在某时还款。", "你不能保证什么时候还款。"));

		List<String> importantFlags = Arrays.asList("你会询问我自己的手机号码从哪获得的。", "你会提及客服存在骚扰行为。", "你会提及有投诉的意图。");
		Map<String, List<String>> generalOptionFlags = new HashMap<>();
		generalOptionFlags.put("还款情况", Arrays.asList("你本月可以还款。", "你还在筹钱中。"));
		generalOptionFlags.put("贷款需求与争议", Arrays.asList("你希望本金打折。", "你对金额有异议。"));
		generalOptionFlags.put("客户反馈与满意度", Arrays.asList("你对放款平台有异议。", "你对此前的催收不满。"));
		generalOptionFlags.put("信息查询与核实", Arrays.asList("你会询问客服代表的公司的名称。", "你会询问客服坐席的身份。"));
		generalOptionFlags.put("法律与投诉行为", Arrays.asList("你有投诉的意向。", "你有报警的意向。", "你曾经有过投诉客服的行为。"));
		generalOptionFlags.put("个人背景与职业", Arrays.asList("你做过催收或相关工作。", "你是媒体从业人员。", "你是银行从业人员。", "你是法律从业人员。","你是公务员。", "你有工作。", "你没有工作", "你有收入来源", "你没有收入来源。", "你有共债。"));
		List<String> emotions = Arrays.asList("生气", "厌恶", "恐惧", "开心", "平淡", "悲伤", "惊讶");

		StringBuilder customerSetting = new StringBuilder();

		Random random = new Random();

		// 重要相反的标签
		int importantOppositeFlagsCount = random.nextInt(importantOppositeFlags.size() + 1);
		if (importantOppositeFlagsCount > 0) {
			Set<List<String>> flags = new HashSet<>();
			do {
				flags.add(importantOppositeFlags.get(random.nextInt(importantOppositeFlags.size())));
			} while (flags.size() < importantOppositeFlagsCount);

			for (List<String> flag : flags) {
				customerSetting.append(flag.get(random.nextInt(flag.size()))).append("\n");
			}
		}

		// 重要标签
		int importantFlagsCount = random.nextInt(importantFlags.size() + 1);
		if (importantFlagsCount > 0) {

			Set<String> flags = new HashSet<>();
			do {
				flags.add(importantFlags.get(random.nextInt(importantFlags.size())));
			} while (flags.size() < importantFlagsCount);

			for (String flag: flags) {
				customerSetting.append(flag).append("\n");
			}
		}

		// 非重要标签
		int generalOptionFlagsCount = random.nextInt(generalOptionFlags.size() + 1);
		if (generalOptionFlagsCount > 0) {
			List<String> keys = new ArrayList<>(generalOptionFlags.keySet());

			Set<String> flags = new HashSet<>();
			do {
				flags.add(keys.get(random.nextInt(keys.size())));
			} while (flags.size() < generalOptionFlagsCount);

			for (String flag : flags) {
				List<String> tags = generalOptionFlags.get(flag);
				customerSetting.append(tags.get(random.nextInt(tags.size()))).append("\n");
			}
		}

		// 情绪
		int emotionsCount = random.nextInt(2);
		if (emotionsCount > 0) {
			customerSetting.append("你的态度是").append(emotions.get(random.nextInt(emotions.size()))).append("\n");
			customerSetting.append("你的语气是没有礼貌的、粗鲁的。\n你的回复不超过100个字。\n你的回复非常口语化，有很多语气词。\n");
		}

		String systemPrompt = "你是风格模仿大师，你的职责是配合我进行电话催款的各个场景的对练。我扮演客服，负责催促你还清欠款。你扮演客户对我进行回复，你扮演的客户的角色设置在下面用三个反引号围起来的内容中。整个催款场景大约10到100轮对话。由我扮演的客服先开始说话，你负责每一轮扮演客户进行回复。\n" +
				"```\n" +
				"%s\n" +
				"```\n";

		return String.format(systemPrompt, customerSetting);
	}

	private String tts(String uniqueKey, String text) {

		String type = null;
		String customerJson = redisService.get(String.format(ServiceConstant.MOCK_CUSTOMER_UNIQUE_KEY, uniqueKey));
		CustomerInfo customerInfo = GSON.fromJson(customerJson, CustomerInfo.class);
		if (customerInfo.getGender().equals("男")) {
			type = "003";
		} else {
			type = "002";
		}

//		String type = redisService.get(ServiceConstant.MOCK_TTS_TYPE);
//		if (StringUtils.isBlank(type)) {
//			type = "003";
//		}
		Map<String, Object> params = new HashMap<>();
		params.put("text", text);
		params.put("reference_text", type);
		params.put("reference_audio", type);
		params.put("max_new_tokens", 0);
		params.put("chunk_length", 150);
		params.put("top_p", 0.7);
		params.put("repetition_penalty", 1.5);
		params.put("temperature", 0.7);
		params.put("speaker", null);
		params.put("emotion", null);
		params.put("format", "wav");
		params.put("streaming", false);
		params.put("ref_json", "ref_data.json");
		params.put("ref_base", "ref_data");

		try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
			// 创建HttpPost对象
			HttpPost post = new HttpPost(ttsUrl);
			post.setHeader(HTTP.CONTENT_TYPE, "application/json; charset=utf-8");
			// 设置请求体
			post.setEntity(new StringEntity(GSON.toJson(params), "UTF-8"));
			// 执行POST请求
			try (CloseableHttpResponse response = httpClient.execute(post)) {
				// 获取响应实体
				HttpEntity entity = response.getEntity();
				if (entity != null) {
					// 输入流获取内容
					String tempFileName = System.currentTimeMillis() + ".wav";
					File file = new File(tempFileName);
					InputStream inputStream = entity.getContent();
					try (OutputStream outputStream = new FileOutputStream(file)) {
						int bytesRead;
						byte[] buffer = new byte[1024];
						while ((bytesRead = inputStream.read(buffer)) != -1) {
							outputStream.write(buffer, 0, bytesRead);
						}
					}
					// 确保实体内容完全消耗
					EntityUtils.consume(entity);
					String audioUrl = saveVoiceFile(uniqueKey, null, new FileInputStream(file), tempFileName);
					file.delete();
					return audioUrl;
				}
			}
		} catch (Exception ex) {
			logger.error("TTS转写错误, text={}, 错误信息=", text, ex);
		}
		return null;
	}

	class ScoreLLMResponse {
		private String reason;
		private Integer score;

		public String getReason() {
			return reason;
		}

		public void setReason(String reason) {
			this.reason = reason;
		}

		public Integer getScore() {
			return score;
		}

		public void setScore(Integer score) {
			this.score = score;
		}
	}

}
